/*
 * Copyright 2024 ywcai
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

`include "defines.v"
`timescale 1ns/1ps

module ctrl(
    input	wire[6:0]			id_opcode_i,
    input	wire[2:0] 			id_funct3_i,
    input	wire[6:0] 			id_funct7_i,
	input	wire				ex_mem_i_inst_auipc_i,
	input	wire[`IrqBus]		irq_i,
	input	wire				trap_flush_id_ex_req_i,
	input	wire[`MemAddrBus]	trap_entry_i,
    input   wire                div_stall_req_i,
	input	wire				ex_mem_req_stall_i,
	input	wire[`MemAddrBus]	ex_pc_target_i,
	input	wire				ex_mem_regwrite_en_i,
	input	wire				mem_wb_regwrite_en_i,
	input	wire[`RegAddrBus]	id_ex_rs1_addr_i,
	input	wire[`RegAddrBus]	id_ex_rs2_addr_i,
	input	wire[2:0]			id_ex_mux_result_src_i,
	input	wire				ex_mux_pcsrc_i,
	input	wire[`RegAddrBus]	if_id_rs1_addr_i,
	input	wire[`RegAddrBus]	if_id_rs2_addr_i,
	input	wire[`RegAddrBus]	id_ex_rd_addr_i,
	input	wire[`RegAddrBus]	ex_mem_rd_addr_i,
	input	wire[`RegAddrBus]	mem_wb_rd_addr_i,
	input	wire				icache_load_stall_req_i,
	input	wire				dcache_load_stall_req_i,

	output	wire				i_inst_auipc_o,
	output	wire				regwrite_en_o,
	output	wire			    mux_alu_src1_o,
	output	wire				mux_alu_src2_o,
	output	wire[2:0]			mux_result_src_o,
	output	wire				mem_req_o,
	output	wire				mem_we_o,
	output	wire				mux_jump_o,
	output	wire				mux_branch_o,
	output	wire[3:0]			mux_alu_ctrl_o,
    output  wire[3:0]           mux_mul_ctrl_o,
	output	wire				mux_pctarget_src_o,
	output	wire				mux_loadtype_o,
	output	wire				mux_storetype_o,
	output	wire[2:0]			mux_imm_src_o,
	output	wire				mux_pcsrc_o,
	output	wire[`MemAddrBus]	jump_target_o,
	output	wire[1:0]			mux_forward_rs1_o,
	output	wire[1:0]			mux_forward_rs2_o,
	output	wire				w_suffix_o,
	output	wire[`IrqBus]		irq_o,
	output	wire				stall_if_req_o,
	output	wire				stall_id_req_o,
    output  wire                stall_ex_req_o,
	output	wire				stall_mem_req_o,
	output	wire				flush_id_req_o,
	output	wire				flush_ex_req_o,
	output	wire				flush_mem_req_o
	);

	wire i_inst_i_type	= (id_opcode_i == `I_TYPE);
	wire i_inst_iw_type	= (id_opcode_i == `IW_TYPE);
	wire i_inst_b_type	= (id_opcode_i == `B_TYPE);
	wire i_inst_r_type	= (id_opcode_i == `R_TYPE);
	wire i_inst_rw_type	= (id_opcode_i == `RW_TYPE);
	wire i_inst_auipc	= (id_opcode_i == `INST_AUIPC);
	wire i_inst_load	= (id_opcode_i == `INST_LOAD);
	wire i_inst_store	= (id_opcode_i == `INST_STORE);
	wire i_inst_jal		= (id_opcode_i == `INST_JAL);
	wire i_inst_jalr	= (id_opcode_i == `INST_JALR);
	wire i_inst_lui		= (id_opcode_i == `INST_LUI);
	wire i_inst_sys		= (id_opcode_i == `INST_SYS);
	wire i_inst_slli	= (i_inst_i_type && (id_funct3_i == `FUNCT3_001));
	wire i_inst_srli	= (i_inst_i_type && (id_funct3_i == `FUNCT3_101) && !id_funct7_i[5]);
	wire i_inst_srai	= (i_inst_i_type && (id_funct3_i == `FUNCT3_101) && id_funct7_i[5]);
	wire i_inst_mul		= ((i_inst_r_type || i_inst_rw_type) && (id_funct3_i == `FUNCT3_000)
		&& id_funct7_i[0]);
	wire i_inst_mulh	= (i_inst_r_type && (id_funct3_i == `FUNCT3_001) && id_funct7_i[0]);
	wire i_inst_mulhsu	= (i_inst_r_type && (id_funct3_i == `FUNCT3_010) && id_funct7_i[0]);
	wire i_inst_mulhu	= (i_inst_r_type && (id_funct3_i == `FUNCT3_011) && id_funct7_i[0]);
	wire i_inst_div		= (i_inst_r_type && (id_funct3_i == `FUNCT3_100) && id_funct7_i[0]);
	wire i_inst_divu	= (i_inst_r_type && (id_funct3_i == `FUNCT3_101) && id_funct7_i[0]);
	wire i_inst_rem		= (i_inst_r_type && (id_funct3_i == `FUNCT3_110) && id_funct7_i[0]);
	wire i_inst_remu	= (i_inst_r_type && (id_funct3_i == `FUNCT3_111) && id_funct7_i[0]);
	wire i_inst_m_cat	= i_inst_r_type && id_funct7_i[0];

	assign i_inst_auipc_o = i_inst_auipc;

	assign regwrite_en_o = !(i_inst_b_type || i_inst_store);

	assign mux_alu_src1_o = i_inst_lui;

	assign mux_alu_src2_o = !(i_inst_r_type || i_inst_rw_type
		|| i_inst_b_type || i_inst_m_cat);

	assign mux_result_src_o = ({3{i_inst_load}} & 3'b001)
        | ({3{i_inst_jal | i_inst_jalr}} & 3'b010)
        | ({3{i_inst_auipc}} & 3'b011);

	assign mem_req_o = i_inst_load || i_inst_store;
	assign mem_we_o = i_inst_store;

	assign mux_jump_o = (i_inst_jal || i_inst_jalr);

	assign mux_branch_o = i_inst_b_type;

	wire alu_add_ls = (i_inst_load || i_inst_store);
	wire alu_add_addi = (i_inst_i_type || i_inst_iw_type)
		&& (id_funct3_i == `FUNCT3_000);
	wire alu_add_add = (i_inst_r_type || i_inst_rw_type)
		&& (id_funct3_i == `FUNCT3_000) & !id_funct7_i[5];
	wire alu_add_lui = i_inst_lui;
	wire alu_add_jalr = i_inst_jalr;
	wire i_alu_op_add = (alu_add_ls || alu_add_addi || alu_add_add
		|| alu_add_lui || alu_add_jalr);

	wire alu_sub_sub = (i_inst_r_type || i_inst_rw_type)
		&& (id_funct3_i == `FUNCT3_000) && id_funct7_i[5];
	wire alu_sub_branch = (id_opcode_i == `B_TYPE);
	wire i_alu_op_sub = (alu_sub_sub || alu_sub_branch);

	wire i_alu_op_and = (i_inst_r_type || i_inst_i_type)
		&& (id_funct3_i == `FUNCT3_111);
	wire i_alu_op_or = (i_inst_r_type || i_inst_i_type)
		&& (id_funct3_i == `FUNCT3_110);
	wire i_alu_op_xor = (i_inst_r_type || i_inst_i_type)
		&& (id_funct3_i == `FUNCT3_100);
	wire i_alu_op_slt = (i_inst_r_type || i_inst_i_type)
		&& (id_funct3_i == `FUNCT3_010);
    wire i_alu_op_sltu = (i_inst_r_type || i_inst_i_type)
		&& (id_funct3_i == `FUNCT3_011);
	wire i_alu_op_sll =
		(i_inst_r_type || i_inst_rw_type || i_inst_i_type || i_inst_iw_type)
		&& (id_funct3_i == `FUNCT3_001);
	wire i_alu_op_srl =
		((i_inst_r_type || i_inst_rw_type || i_inst_i_type || i_inst_iw_type)
		&& !id_funct7_i[5]) && (id_funct3_i == `FUNCT3_101);
	wire i_alu_op_sra =
		((i_inst_r_type || i_inst_rw_type || i_inst_i_type || i_inst_iw_type)
		&& id_funct7_i[5]) && (id_funct3_i == `FUNCT3_101);

	assign mux_alu_ctrl_o = ({4{i_alu_op_add}} & 4'b0000)
        | ({4{i_alu_op_sub}} & 4'b0001)
        | ({4{i_alu_op_and}} & 4'b0010)
        | ({4{i_alu_op_or}} & 4'b0011)
        | ({4{i_alu_op_xor}} & 4'b0100)
        | ({4{i_alu_op_slt}} & 4'b0101)
        | ({4{i_alu_op_sltu}} & 4'b0110)
        | ({4{i_alu_op_sll}} & 4'b0111)
        | ({4{i_alu_op_srl}} & 4'b1000)
        | ({4{i_alu_op_sra}} & 4'b1001);

    assign mux_mul_ctrl_o = ({4{i_inst_mul}} & 4'b0001)
        | ({4{i_inst_mulh}} & 4'b0010)
        | ({4{i_inst_mulhu}} & 4'b0011)
        | ({4{i_inst_mulhsu}} & 4'b0100)
        | ({4{i_inst_div}} & 4'b0101)
        | ({4{i_inst_divu}} & 4'b0110)
        | ({4{i_inst_rem}} & 4'b0111)
        | ({4{i_inst_remu}} & 4'b1000);

	assign mux_imm_src_o = ({3{i_inst_store}} & 3'b001)
        | ({3{i_inst_b_type}} & 3'b010)
        | ({3{i_inst_jal}} & 3'b011)
        | ({3{i_inst_lui || i_inst_auipc}} & 3'b100)
        | ({3{i_inst_slli || i_inst_srli || i_inst_srai}} & 3'b101);

	assign mux_pctarget_src_o = i_inst_jalr;

	assign mux_loadtype_o = (i_inst_load && (id_funct3_i == `FUNCT3_011));

	assign mux_storetype_o = (i_inst_store && (id_funct3_i == `FUNCT3_011));

	assign jump_target_o = trap_flush_id_ex_req_i ? trap_entry_i
		: ex_pc_target_i;

	wire ex_mem_hazard_rs1 = ex_mem_regwrite_en_i
		&& (ex_mem_rd_addr_i != 5'h0)
		&& (ex_mem_rd_addr_i == id_ex_rs1_addr_i);

	wire mem_wb_hazard_rs1 = mem_wb_regwrite_en_i
		&& (mem_wb_rd_addr_i != 5'h0)
		&& (mem_wb_rd_addr_i == id_ex_rs1_addr_i);

	wire ex_mem_hazard_rs2 = ex_mem_regwrite_en_i
		&& (ex_mem_rd_addr_i != 5'h0)
		&& (ex_mem_rd_addr_i == id_ex_rs2_addr_i);

	wire mem_wb_hazard_rs2 = mem_wb_regwrite_en_i
		&& (mem_wb_rd_addr_i != 5'h0)
		&& (mem_wb_rd_addr_i == id_ex_rs2_addr_i);

	assign mux_forward_rs1_o = (ex_mem_hazard_rs1 && ex_mem_i_inst_auipc_i) ? 2'b11
		: (ex_mem_hazard_rs1 ? 2'b10
		: (mem_wb_hazard_rs1 ? 2'b01: 2'b00));

	assign mux_forward_rs2_o = (ex_mem_hazard_rs2 && ex_mem_i_inst_auipc_i) ? 2'b11
		: (ex_mem_hazard_rs2 ? 2'b10
		: (mem_wb_hazard_rs2 ? 2'b01 : 2'b00));

	assign w_suffix_o = i_inst_iw_type || i_inst_rw_type;

	wire load_stall_req = (id_ex_mux_result_src_i == 3'b001)
		&& ((id_ex_rd_addr_i == if_id_rs1_addr_i)
			|| (id_ex_rd_addr_i == if_id_rs2_addr_i));

	assign stall_if_req_o = icache_load_stall_req_i || dcache_load_stall_req_i
        || div_stall_req_i || load_stall_req || ex_mem_req_stall_i;
	assign stall_id_req_o = icache_load_stall_req_i || dcache_load_stall_req_i
		|| div_stall_req_i || load_stall_req || ex_mem_req_stall_i;
    assign stall_ex_req_o = dcache_load_stall_req_i
		|| div_stall_req_i || ex_mem_req_stall_i;
	// since inst cache load may occur at same time with data cache load, so need stall mem stage
	assign stall_mem_req_o = dcache_load_stall_req_i;
	assign flush_id_req_o = trap_flush_id_ex_req_i || ex_mux_pcsrc_i;
	assign flush_ex_req_o = (icache_load_stall_req_i && !dcache_load_stall_req_i
			&& !div_stall_req_i)
		|| load_stall_req || trap_flush_id_ex_req_i || ex_mux_pcsrc_i;
	assign flush_mem_req_o = ex_mem_req_stall_i;

    assign mux_pcsrc_o = trap_flush_id_ex_req_i ? `ENABLE : ex_mux_pcsrc_i;

endmodule
